/*
 * RPA format (works without python):
 *
 * Starts with a newline terminated, space separated list. The first is the magic "RPA-(major).(minor)", the rest is hex encoded
 * numbers. The first number is a file offset, the others must be XOR'd together to get the key. RPA-3.2 does not use the second
 * number for this. At the end of the file (starting at the first number) is a zlib compressed blob. Once uncompressed, it is
 * a Python pickle bytecode. Decoding the bytecode gives path + offset + length triplets, but latter two are XOR'd with the key.
 * We do not interpret the pickle opcodes, we just keep a state machine and get the unicode string and the two binary numbers
 * following it, and we repeat this for each tuple.
 *
 * RPYC format (have to have python, can't work otherwise):
 *
 * Starts with magic "RENPY RPC2", followed by 3 * 4 bytes (slot, offset, length) records. Each slot might be zlib compressed
 * (starts with 0x78), base64 (only a-zA-Z0-9+=\n) or hex (only a-fA-F0-9) encoded. We are only interested in the slot with id 1,
 * after zlib / base64 / etc. you got a pickle encoded renpy AST tree (starts with 0x80 0x02). You'll need pyton and a fake renpy
 * class to unpickle that, and also an ATLDecompiler to convert that AST tree back to a string to get the script's source...
 * See https://github.com/CensoredUsername/unrpyc
 *
 * After we have extracted the .rpyc files from the RPA archive, we will extract the built-in unrpyc and execute it which will
 * (hopefully) create the decrypted .rpy files. On Windows, we use the python.exe shipped with the game for that.
 */
#include "main.h"
#include "../zlib.h"
#ifdef __WIN32__
#include <windows.h>
#else
#include <sys/stat.h>
#endif

/* Pyhton pickle bytecode */
#define PA_NOARG 0
#define PA_NL    10
#define PA_NUM   0x20
#define PA_FN    0x40
#define PA_BUF   0x80
static uint8_t pickles[256] = {
    /* 00   00 */ PA_NOARG,             /* 01   00 */ PA_NOARG,             /* 02   00 */ PA_NOARG,             /* 03   00 */ PA_NOARG,
    /* 04   00 */ PA_NOARG,             /* 05   00 */ PA_NOARG,             /* 06   00 */ PA_NOARG,             /* 07   00 */ PA_NOARG,
    /* 08   00 */ PA_NOARG,             /* 09   00 */ PA_NOARG,             /* 0a   00 */ PA_NOARG,             /* 0b   00 */ PA_NOARG,
    /* 0c   00 */ PA_NOARG,             /* 0d   00 */ PA_NOARG,             /* 0e   00 */ PA_NOARG,             /* 0f   00 */ PA_NOARG,
    /* 10   00 */ PA_NOARG,             /* 11   00 */ PA_NOARG,             /* 12   00 */ PA_NOARG,             /* 13   00 */ PA_NOARG,
    /* 14   00 */ PA_NOARG,             /* 15   00 */ PA_NOARG,             /* 16   00 */ PA_NOARG,             /* 17   00 */ PA_NOARG,
    /* 18   00 */ PA_NOARG,             /* 19   00 */ PA_NOARG,             /* 1a   00 */ PA_NOARG,             /* 1b   00 */ PA_NOARG,
    /* 1c   00 */ PA_NOARG,             /* 1d   00 */ PA_NOARG,             /* 1e   00 */ PA_NOARG,             /* 1f   00 */ PA_NOARG,
    /* 20   00 */ PA_NOARG,             /* 21 ! 00 */ PA_NOARG,             /* 22 " 00 */ PA_NOARG,             /* 23 # 00 */ PA_NOARG,
    /* 24 $ 00 */ PA_NOARG,             /* 25 % 00 */ PA_NOARG,             /* 26 & 00 */ PA_NOARG,             /* 27 ' 00 */ PA_NOARG,
    /* 28 ( 00 */ PA_NOARG,             /* 29 ) 00 */ PA_NOARG,             /* 2a * 00 */ PA_NOARG,             /* 2b + 00 */ PA_NOARG,
    /* 2c , 00 */ PA_NOARG,             /* 2d - 00 */ PA_NOARG,             /* 2e . 00 */ PA_NOARG,             /* 2f / 00 */ PA_NOARG,
    /* 30 0 00 */ PA_NOARG,             /* 31 1 00 */ PA_NOARG,             /* 32 2 00 */ PA_NOARG,             /* 33 3 00 */ PA_NOARG,
    /* 34 4 00 */ PA_NOARG,             /* 35 5 00 */ PA_NOARG,             /* 36 6 00 */ PA_NOARG,             /* 37 7 00 */ PA_NOARG,
    /* 38 8 00 */ PA_NOARG,             /* 39 9 00 */ PA_NOARG,             /* 3a : 00 */ PA_NOARG,             /* 3b ; 00 */ PA_NOARG,
    /* 3c < 00 */ PA_NOARG,             /* 3d = 00 */ PA_NOARG,             /* 3e > 00 */ PA_NOARG,             /* 3f ? 00 */ PA_NOARG,
    /* 40 @ 00 */ PA_NOARG,             /* 41 A 00 */ PA_NOARG,             /* 42 B 84 */ PA_BUF|4,             /* 43 C 81 */ PA_BUF|1,
    /* 44 D 00 */ PA_NOARG,             /* 45 E 00 */ PA_NOARG,             /* 46 F 0a */ PA_NL,                /* 47 G 08 */ 8,
    /* 48 H 00 */ PA_NOARG,             /* 49 I 0a */ PA_NL,                /* 4a J 24 */ PA_NUM|4,             /* 4b K 21 */ PA_NUM|1,
    /* 4c L 0a */ PA_NL,                /* 4d M 22 */ PA_NUM|2,             /* 4e N 00 */ PA_NOARG,             /* 4f O 00 */ PA_NOARG,
    /* 50 P 0a */ PA_NL,                /* 51 Q 00 */ PA_NOARG,             /* 52 R 00 */ PA_NOARG,             /* 53 S 0a */ PA_NL,
    /* 54 T c4 */ PA_BUF|PA_FN|4,       /* 55 U c1 */ PA_BUF|PA_FN|1,       /* 56 V ca */ PA_BUF|PA_FN|PA_NL,   /* 57 W 00 */ PA_NOARG,
    /* 58 X c4 */ PA_BUF|PA_FN|4,       /* 59 Y 00 */ PA_NOARG,             /* 5a Z 00 */ PA_NOARG,             /* 5b [ 00 */ PA_NOARG,
    /* 5c \ 00 */ PA_NOARG,             /* 5d ] 00 */ PA_NOARG,             /* 5e ^ 00 */ PA_NOARG,             /* 5f _ 00 */ PA_NOARG,
    /* 60 ` 00 */ PA_NOARG,             /* 61 a 00 */ PA_NOARG,             /* 62 b 00 */ PA_NOARG,             /* 63 c 0a */ PA_NL,
    /* 64 d 00 */ PA_NOARG,             /* 65 e 00 */ PA_NOARG,             /* 66 f 00 */ PA_NOARG,             /* 67 g 0a */ PA_NL,
    /* 68 h 01 */ 1,                    /* 69 i 0a */ PA_NL,                /* 6a j 04 */ 4,                    /* 6b k 00 */ PA_NOARG,
    /* 6c l 00 */ PA_NOARG,             /* 6d m 00 */ PA_NOARG,             /* 6e n 00 */ PA_NOARG,             /* 6f o 00 */ PA_NOARG,
    /* 70 p 0a */ PA_NL,                /* 71 q 01 */ 1,                    /* 72 r 04 */ 4,                    /* 73 s 00 */ PA_NOARG,
    /* 74 t 00 */ PA_NOARG,             /* 75 u 00 */ PA_NOARG,             /* 76 v 00 */ PA_NOARG,             /* 77 w 00 */ PA_NOARG,
    /* 78 x 00 */ PA_NOARG,             /* 79 y 00 */ PA_NOARG,             /* 7a z 00 */ PA_NOARG,             /* 7b { 00 */ PA_NOARG,
    /* 7c | 00 */ PA_NOARG,             /* 7d } 00 */ PA_NOARG,             /* 7e ~ 00 */ PA_NOARG,             /* 7f   00 */ PA_NOARG,
    /* 80   01 */ 1,                    /* 81   00 */ PA_NOARG,             /* 82   01 */ 1,                    /* 83   02 */ 2,
    /* 84   04 */ 4,                    /* 85   00 */ PA_NOARG,             /* 86   00 */ PA_NOARG,             /* 87   00 */ PA_NOARG,
    /* 88   00 */ PA_NOARG,             /* 89   00 */ PA_NOARG,             /* 8a   21 */ PA_NUM|1,             /* 8b   24 */ PA_NUM|4,
    /* 8c   c1 */ PA_BUF|PA_FN|1,       /* 8d   c8 */ PA_BUF|PA_FN|8,       /* 8e   88 */ PA_BUF|8,             /* 8f   00 */ PA_NOARG,
    /* 90   00 */ PA_NOARG,             /* 91   00 */ PA_NOARG,             /* 92   00 */ PA_NOARG,             /* 93   00 */ PA_NOARG,
    /* 94   00 */ PA_NOARG,             /* 95   08 */ 8,                    /* 96   88 */ PA_BUF|8,             /* 97   00 */ PA_NOARG,
    /* 98   00 */ PA_NOARG,             /* 99   00 */ PA_NOARG,             /* 9a   00 */ PA_NOARG,             /* 9b   00 */ PA_NOARG,
    /* 9c   00 */ PA_NOARG,             /* 9d   00 */ PA_NOARG,             /* 9e   00 */ PA_NOARG,             /* 9f   00 */ PA_NOARG,
    /* a0   00 */ PA_NOARG,             /* a1   00 */ PA_NOARG,             /* a2   00 */ PA_NOARG,             /* a3   00 */ PA_NOARG,
    /* a4   00 */ PA_NOARG,             /* a5   00 */ PA_NOARG,             /* a6   00 */ PA_NOARG,             /* a7   00 */ PA_NOARG,
    /* a8   00 */ PA_NOARG,             /* a9   00 */ PA_NOARG,             /* aa   00 */ PA_NOARG,             /* ab   00 */ PA_NOARG,
    /* ac   00 */ PA_NOARG,             /* ad   00 */ PA_NOARG,             /* ae   00 */ PA_NOARG,             /* af   00 */ PA_NOARG,
    /* b0   00 */ PA_NOARG,             /* b1   00 */ PA_NOARG,             /* b2   00 */ PA_NOARG,             /* b3   00 */ PA_NOARG,
    /* b4   00 */ PA_NOARG,             /* b5   00 */ PA_NOARG,             /* b6   00 */ PA_NOARG,             /* b7   00 */ PA_NOARG,
    /* b8   00 */ PA_NOARG,             /* b9   00 */ PA_NOARG,             /* ba   00 */ PA_NOARG,             /* bb   00 */ PA_NOARG,
    /* bc   00 */ PA_NOARG,             /* bd   00 */ PA_NOARG,             /* be   00 */ PA_NOARG,             /* bf   00 */ PA_NOARG,
    /* c0   00 */ PA_NOARG,             /* c1   00 */ PA_NOARG,             /* c2   00 */ PA_NOARG,             /* c3   00 */ PA_NOARG,
    /* c4   00 */ PA_NOARG,             /* c5   00 */ PA_NOARG,             /* c6   00 */ PA_NOARG,             /* c7   00 */ PA_NOARG,
    /* c8   00 */ PA_NOARG,             /* c9   00 */ PA_NOARG,             /* ca   00 */ PA_NOARG,             /* cb   00 */ PA_NOARG,
    /* cc   00 */ PA_NOARG,             /* cd   00 */ PA_NOARG,             /* ce   00 */ PA_NOARG,             /* cf   00 */ PA_NOARG,
    /* d0   00 */ PA_NOARG,             /* d1   00 */ PA_NOARG,             /* d2   00 */ PA_NOARG,             /* d3   00 */ PA_NOARG,
    /* d4   00 */ PA_NOARG,             /* d5   00 */ PA_NOARG,             /* d6   00 */ PA_NOARG,             /* d7   00 */ PA_NOARG,
    /* d8   00 */ PA_NOARG,             /* d9   00 */ PA_NOARG,             /* da   00 */ PA_NOARG,             /* db   00 */ PA_NOARG,
    /* dc   00 */ PA_NOARG,             /* dd   00 */ PA_NOARG,             /* de   00 */ PA_NOARG,             /* df   00 */ PA_NOARG,
    /* e0   00 */ PA_NOARG,             /* e1   00 */ PA_NOARG,             /* e2   00 */ PA_NOARG,             /* e3   00 */ PA_NOARG,
    /* e4   00 */ PA_NOARG,             /* e5   00 */ PA_NOARG,             /* e6   00 */ PA_NOARG,             /* e7   00 */ PA_NOARG,
    /* e8   00 */ PA_NOARG,             /* e9   00 */ PA_NOARG,             /* ea   00 */ PA_NOARG,             /* eb   00 */ PA_NOARG,
    /* ec   00 */ PA_NOARG,             /* ed   00 */ PA_NOARG,             /* ee   00 */ PA_NOARG,             /* ef   00 */ PA_NOARG,
    /* f0   00 */ PA_NOARG,             /* f1   00 */ PA_NOARG,             /* f2   00 */ PA_NOARG,             /* f3   00 */ PA_NOARG,
    /* f4   00 */ PA_NOARG,             /* f5   00 */ PA_NOARG,             /* f6   00 */ PA_NOARG,             /* f7   00 */ PA_NOARG,
    /* f8   00 */ PA_NOARG,             /* f9   00 */ PA_NOARG,             /* fa   00 */ PA_NOARG,             /* fb   00 */ PA_NOARG,
    /* fc   00 */ PA_NOARG,             /* fd   00 */ PA_NOARG,             /* fe   00 */ PA_NOARG,             /* ff   00 */ PA_NOARG
};
extern uint8_t unrpyc_zip[];

/* file index */
typedef struct {
    char name[NAMELEN];
    uint64_t offs, size;
} index_t;
static index_t *idx = NULL;
static int numidx;
static uint64_t total, current;

/**
 * Unpack and execute unrpyc to deobfuscate python scripts
 */
static void unrpyc(char *path)
{
    uLongf size;
    uint8_t *ptr, *buf;
    char tmp[PATH_MAX + FILENAME_MAX + 1], cmd[3 * (PATH_MAX + FILENAME_MAX + NAMELEN) + 1], *fn, *s, *d;
    char py[PATH_MAX + FILENAME_MAX + 1];
    int ret;
#ifdef __WIN32__
    wchar_t wCmd[3 * (PATH_MAX + FILENAME_MAX) + 1];
#endif

    if(quit || !path || !*path) return;
    strcpy(tmp, path);
    fn = tmp + strlen(tmp);
    /* extract the built-in unrpyc into the game's folder */
    strcpy(fn, "/unrpyc/unrpyc.py");
    if(!fileexists(tmp)) {
        fn += 7;
        *fn = 0; filemkdir(tmp);
        debug("extracting to '%s'", tmp);
        strcpy(fn, "/decompiler"); filemkdir(tmp);
        for(fn++, ptr = unrpyc_zip; ptr[0] || ptr[1]; ptr += (ptr[0] | (ptr[1] << 8)) + ptr[5] + 6) {
            memcpy(fn, ptr + 6, ptr[5]); fn[(uint32_t)ptr[5]] = 0;
            size = ptr[2] | (ptr[3] << 8) | (ptr[4] << 16);
            if((buf = (uint8_t*)malloc(size))) {
                uncompress(buf, &size, ptr + 6 + ptr[5], ptr[0] | (ptr[1] << 8));
                putfile(tmp, buf, size, NULL, 0, NULL, 0);
                free(buf);
            } else {
                engine_features |= EF_NOMEM;
                debug("unable to allocate memory for unpacking unrypc (%lu bytes)", size);
            }
        }
        fn -= 8; *fn = 0;
    }
    /* and execute it with the game files */
    strcpy(cmd, tmp);
    for(s = cmd, d = tmp; *s; s++) {
        if(*s == '\"') { *d++ = '\\'; *d++ = *s; }
        else if(*s >= ' ') *d++ = *s;
    }
    *d = 0;
#ifdef __WIN32__
    strcpy(py, cmd); fn = py + strlen(py); strcpy(fn, "/lib/py3-windows-x86_64/python.exe");
    if(!fileexists(py)) {
        debug(0, "renpy game missing '%s'", py);
        strcpy(fn, "/lib/py3-windows-i686/python.exe");
    }
    sprintf(cmd, "\"%s%s\" -B \"%s/unrpyc/unrpyc.py\" \"%s/game\"", tmp, fn, tmp, tmp);
    memset(wCmd, 0, sizeof(wCmd));
    MultiByteToWideChar(CP_UTF8, 0, cmd, -1, wCmd, sizeof(wCmd));
    fflush(stdout);
    ret = _wsystem(wCmd);
#else
    strcpy(py, cmd); strcat(py, "/lib/py3-linux-x86_64/python");
    if(fileexists(py)) {
        chmod(py, 0555);
        strcpy(py, tmp); strcat(py, "/lib/py3-linux-x86_64/python");
    } else {
        debug(0, "renpy game missing '%s'", py);
        strcpy(py, "python");
    }
    sprintf(cmd, "\"%s\" -B \"%s/unrpyc/unrpyc.py\" \"%s/game\"", py, tmp, tmp);
    ret = system(cmd);
#endif
    fflush(stdout);
    debug("command '%s' returned %d", cmd, ret);
}

/**
 * Decrypt an RPA and construct file index
 */
static uint64_t decrypt_file(uint8_t *buf, uint64_t len)
{
    uint8_t *ptr, *ptr2, *pck = NULL, op;
    uint64_t offs, size, arg, key = 0;
    int i, j = 0;

    numidx = 0;
    if(idx) { free(idx); idx = NULL; }
    for(ptr = buf; ptr < buf + 32 && *ptr != ' '; ptr++);
    for(; ptr < buf + 32 && *ptr == ' '; ptr++);
    for(ptr2 = ptr; ptr2 < buf + 32 && *ptr2 != ' '; ptr2++);
    offs = gethex((char*)ptr, (uintptr_t)ptr2 - (uintptr_t)ptr);
    size = (len - offs) << 7;
    for(ptr = ptr2, i = 0; ptr < buf + 4096 && *ptr != '\n'; ptr = ptr2, i++) {
        while(*ptr == ' ') ptr++;
        for(ptr2 = ptr; ptr2 < buf + 4096 && *ptr2 != ' ' && *ptr2 != '\n'; ptr2++);
        key ^= gethex((char*)ptr, (uintptr_t)ptr2 - (uintptr_t)ptr);
        /* 3.2 does not use the second number, skip it */
        if(!i && !(buf[4] <= '3' && buf[6] == '0')) key = 0;
    }
    if(offs + 32 < len && (pck = (uint8_t*)malloc(size))) {
        uncompress(pck, (uLongf*)&size, buf + offs, len - offs);
        if(size && pck[0] == 0x80) {
            if(pck[1] > 5) debug("warning: pickle protocol %u (supported 5)", pck[1]);
            /* count unicode strings in pickle */
            for(ptr = pck, ptr2 = pck + size; ptr < ptr2;) {
                op = *ptr++;
                switch(pickles[op] & 0x0F) {
                    case 1: arg = ptr[0]; ptr++; break;
                    case 2: arg = ptr[0] | (ptr[1] << 8); ptr += 2; break;
                    case 4: memcpy(&arg, ptr, 4); ptr += 4; break;
                    case 8: memcpy(&arg, ptr, 8); ptr += 8; break;
                    case '\n': while(ptr < ptr2 && ptr[-1] != '\n') ptr++; break;
                }
                if(pickles[op] & 0x40) numidx++;
                if(pickles[op] & 0x80) ptr += arg;
            }
            /* get data */
            if(numidx && (idx = (index_t*)malloc(numidx * sizeof(index_t))))
                for(ptr = pck, i = -1; !quit && ptr < ptr2 && i < numidx;) {
                    op = *ptr++;
                    switch(pickles[op] & 0x0F) {
                        case 1: arg = ptr[0]; ptr++; break;
                        case 2: arg = ptr[0] | (ptr[1] << 8); ptr += 2; break;
                        case 4: memcpy(&arg, ptr, 4); ptr += 4; break;
                        case 8: memcpy(&arg, ptr, 8); ptr += 8; break;
                        case '\n': while(ptr < ptr2 && ptr[-1] != '\n') ptr++; break;
                    }
                    if(pickles[op] & 0x20 && i >= 0) {
                        if(!j++) idx[i].offs = arg ^ key; else {
                            idx[i].size = arg ^ key;
                            if(verbose > 1) debug("file in archive '%s' (%lu bytes)", idx[i].name, idx[i].size);
                        }
                    }
                    if(pickles[op] & 0x40) { j = arg > (NAMELEN-1) ? (NAMELEN-1) : arg; i++; memcpy(idx[i].name, ptr, j); idx[i].name[j] = 0; j = 0; }
                    if(pickles[op] & 0x80) ptr += arg;
                }
        } else debug("warning: bad pickle proto %02x != 80", pck[0]);
        free(pck);
    } else {
        engine_features |= EF_NOMEM;
        debug("unable to allocate memory for pickle buffer (%lu bytes)", size);
    }
    return len;
}

/**
 * Extract one file from an RPA
 */
static void unpack_file(char *path, uint8_t *buf, uint64_t len)
{
    uint64_t sub = 0;
    char *fn, *s;
    int i;

    if(!path || !*path || !buf || !len || !idx || !numidx) return;
    for(fn = path + strlen(path); fn > path + 1 && *fn != '/' && *fn != '\\' && *fn != '.'; fn--);
    if(*fn != '.') *fn++ = '~';
    *fn = 0; filemkdir(path);
    debug("unpacking to '%s'", path);
    *fn++ = '/'; *fn = 0;
    for(i = 0; !quit && i < numidx; i++) {
        strcpy(fn, idx[i].name);
        for(s = fn; *s; s++)
            if(*s == '/') { *s = 0; filemkdir(path); *s = '/'; }
        if(idx[i].offs < len && idx[i].offs + idx[i].size < len)
            putfile(path, buf + idx[i].offs, idx[i].size, NULL, 0, NULL, 0);
        sub += idx[i].size;
        status(SAVING, current + sub, total);
    }
}

/**
 * Detect if this is a Ren'Py game
 */
int renpy_detect(char *path, uint8_t *buf, uint64_t size)
{
    char tmp[PATH_MAX + FILENAME_MAX + 1], *fn;

    (void)size;
    if(!path || !*path) return 0;
    if(buf && size > 32 && !memcmp(buf, "RPA-", 4)) return 1;

    strcpy(tmp, path);
    fn = tmp + strlen(tmp);
    strcpy(fn, "/renpy/__init__.py");
    if(fileexists(tmp)) return 1;
    strcpy(fn, "/game/archive.rpa");
    if(fileexists(tmp)) return 1;
    strcpy(fn, "/game/assets.rpa");
    return fileexists(tmp);
}

/**
 * Callback function for find() in renpy_load()
 */
int renpy_loadcb(char *path)
{
    uint8_t *buf, *buf2, tmp[32];
    uint64_t size = 0, sub = 0;
    void *f = fileopen(path, &size);
    int i;

    if(f) {
        fileread(f, tmp, sizeof(tmp));
        fileclose(f);
        if(size > 32 && !memcmp(tmp, "RPA-", 4)) {
            if((buf = getfile(path, &size))) {
                decrypt_file(buf, size);
                if(idx) {
                    for(i = 0; !quit && i < numidx; i++) {
                        debug("loading offs %lx size %lu '%s'", idx[i].offs, idx[i].size, idx[i].name);
                        if(idx[i].offs < size && idx[i].offs + idx[i].size < size && (buf2 = (uint8_t*)malloc(idx[i].size))) {
                            memcpy(buf2, buf + idx[i].offs, idx[i].size);
                            if(!fileasset(-1, idx[i].name, buf2, idx[i].size)) free(buf2);
                        } else {
                            engine_features |= EF_NOMEM;
                            debug("unable to allocate memory for packed file (%lu bytes)", idx[i].size);
                        }
                        sub += idx[i].size;
                        status(LOADING, current + sub, total);
                    }
                    free(idx); idx = NULL;
                }
                free(buf);
            }
        } else
        if(size > 32 && fileknown(tmp) && (buf = getfile(path, &size))) {
            if(!fileasset(-1, path + pathlen, buf, size)) free(buf);
        }
    }
    current += size;
    status(LOADING, current, total);
    return 1;
}

/**
 * Load all assets from a Ren'Py game
 */
void renpy_load(char *path, int isdir)
{
    char tmp[PATH_MAX + FILENAME_MAX + 1];
    char *fn;

    if(!path || !*path) return;

    strcpy(tmp, path);

    status(LOADING, 0, 100);
    fn = strrchr(path, '/');
    if(!fn) fn = strrchr(path, '\\');
    if(fn && isdir) strncpy(engine_game, fn + 1, sizeof(engine_game) - 1);
    else strcpy(engine_game, "?");

    pathlen = strlen(tmp);
    current = 0; total = du(tmp);
    find(tmp, 1, renpy_loadcb);

    engine_features |= EF_DECRYPT;
}

/**
 * Free any additional resources that renpy_load() allocated
 */
void renpy_unload(void)
{
    if(idx) { free(idx); idx = NULL; numidx = 0; }
}

/**
 * Callback function for find() in renpy_decrypt()
 */
int renpy_decryptcb(char *path)
{
    uint8_t *buf, tmp[32];
    uint64_t size = 0;
    void *f = fileopen(path, &size);

    if(f) {
        fileread(f, tmp, sizeof(tmp));
        fileclose(f);
        if(size > 32 && !memcmp(tmp, "RPA-", 4)) {
            if((buf = getfile(path, &size))) {
                decrypt_file(buf, size);
                if(idx) { unpack_file(path, buf, size); free(idx); idx = NULL; }
                free(buf);
            }
        }
    }
    current += size;
    status(SAVING, current, total);
    return 1;
}

/**
 * Decrypt and unpack all files in a Ren'Py game
 */
void renpy_decrypt(char *path, int isdir)
{
    uint8_t *buf;
    uint64_t size = 0;
    char tmp[PATH_MAX + FILENAME_MAX + 1];

    if(!path || !*path) return;

    strcpy(tmp, path);

    status(SAVING, 0, 100);
    current = 0;
    if(!isdir) {
        if(((buf = getfile(tmp, &size)) && size > 32 && !memcmp(buf, "RPA-", 4))) {
            total = size;
            decrypt_file(buf, size);
            if(idx) { unpack_file(tmp, buf, size); free(idx); idx = NULL; }
        } else debug("not an RPA '%s'", path);
    } else {
        total = du(tmp) + 1000;
        find(tmp, 1, renpy_decryptcb);
        status(SAVING, total - 1000, total);
        unrpyc(tmp);
    }
}

/**
 * Draw map tiles to off-screen pixel buffer
 */
void renpy_drawmap(map_t *map)
{
    (void)map;
}

/**
 * Save translated text
 */
void renpy_translate(char *path)
{
    msgbox(0, UNIMPLEMENTED, path);
}

ENGINE_MODULE(renpy, "Ren'Py Archive")
